package de.medieninf.sensimg;

import java.io.DataInputStream;
import java.io.IOException;

import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

/**
 * Displays retrieved images and accepts keyboard data.
 * @author pb
 */
public class ImgRecv extends Canvas {
    /**
     * Local cache of settings instance.
     */
    private Settings settings;
    /**
     * Local cache of keyboardState instance.
     */
    private KeyboardState keyboardState;
    /**
     * Download the image.
     */
    private Thread thread;
    /**
     * The requested image type, currently just JPEG.
     */
    private String type = "JPEG";
    /**
     * The query string constructed to download the image.
     */
    private String query;
    /**
     * Lock to protect image operations.
     */
    private Object imgLock = new Object();
    /**
     * The stored image.
     */
    private Image img;
    /**
     * Callback after loading of an image has completed.
     */
    private Runnable callBack;
    /**
     * Last message emitted by ImgRecv.
     */
    private String message;

    /**
     * Upper CommandListener.
     */
    private CommandListener upperCl;
    /**
     * Local CommandListener.
     */
    private CommandListener cl = new CommandListener() {
        public void commandAction(final Command c, final Displayable d) {
            upperCl.commandAction(c, d);
        }
    };

    /**
     * Construct an ImgRecv instance.
     * @param pupperCl upper CommandListener
     * @param cmds the commands that should be set
     * @param pkeyboardState the keyboardState to use
     */
    public ImgRecv(final CommandListener pupperCl, final Command[] cmds,
            final KeyboardState pkeyboardState) {
        this.keyboardState = pkeyboardState;
        this.upperCl = pupperCl;
        for (int i = 0; i < cmds.length; i++) {
            addCommand(cmds[i]);
        }
        this.setCommandListener(cl);
        settings = Settings.getSettings();
    }

    /**
     * Updates query string and starts downloading on visualization.
     */
    protected final void showNotify() {
        query = "?width=" + getWidth() + "&height=" + getHeight()
              + "&type=" + type;
        startHttp();
        super.showNotify();
    }

    /**
     * Stops downloading as soon as is no longer visible.
     */
    protected final void hideNotify() {
        stop();
        super.hideNotify();
    }

    /**
     * Sets the callback.
     * @param pcallBack to set
     */
    public final void setCallBack(final Runnable pcallBack) {
        this.callBack = pcallBack;
    }

    /**
     * Safely gets one image.
     * @return Image image in store or null if none available
     */
    public final Image getImage() {
        Image ret = null;
        synchronized (imgLock) {
            if (this.img != null) {
                ret = img;
                img = null;
            }
        }
        return ret;
    }

    /**
     * Start HTTP receiving thread.
     */
    private void startHttp() {
        thread = new Thread() {
            private static final int BUF_SIZE = 512;
            private static final int SLEEP_MS = 40; // sleep after image

            private byte[] buf = new byte[BUF_SIZE];
            public int readFullyDis(final DataInputStream dis)
                    throws IOException {
                int ch;
                int i = 0;
                while ((ch = dis.read()) != -1) {
                    if (buf.length <= i) {
                        byte[] newBuf = new byte[buf.length * 2];
                        System.arraycopy(buf, 0, newBuf, 0, buf.length);
                        buf = newBuf;
                    }
                    buf[i++] = (byte) ch;
                }
                return i - 1;
            }

            public void serve() {
                // Logger.log("An HTTP round...");
                String recvUrl = settings.getCurrentRecvUrl();
                String url = "http://" + recvUrl + "/" + query;
                HttpConnection hc = null;
                DataInputStream dis = null;
                int len = 0;
                try {
                    Connection c = Connector.open(url, Connector.READ, true);
                    hc = (HttpConnection) c;
                    // Logger.log("opened the connection: " + url);
                    int rc = hc.getResponseCode();
                    if (rc != HttpConnection.HTTP_OK) {
                        try {
                            hc.close();
                        } catch (Exception e) {
                            Logger.log("ImgRecv close hc" + e.getMessage());
                        }
                        throw new IOException("HTTP response code: " + rc);
                    }
                    dis = hc.openDataInputStream();
                    // Logger.log("opened the dis");
                    len = readFullyDis(dis);
                } catch (IOException e) {
                    message = "IOException receiving image " + e.getMessage();
                    Logger.log(message);
                    repaint();
                } finally {
                    try {
                        if (dis !=  null) {
                            dis.close();
                        }
                    } catch (Exception e) {
                        Logger.log("ImgRecv close dis" + e.getMessage());
                    }
                    try {
                        if (hc != null) {
                            hc.close();
                        }
                    } catch (Exception e) {
                        Logger.log("ImgRecv close hc" + e.getMessage());
                    }
                }
                // Logger.log("Read an image of len: " + len);
                if (len <= 0) {
                    Logger.log("error reading image");
                    return;
                }
                Logger.log("image read");
                Image localImg = Image.createImage(buf, 0, buf.length);
                synchronized (imgLock) {
                    img = localImg;
                }
                repaint();
                // Logger.log("image converted");
                if (callBack != null) {
                    callBack.run();
                }
                try {
                    Thread.sleep(SLEEP_MS); // give time for other work
                } catch (InterruptedException e) {
                    Logger.log("interrupt in forced sleep ignored");
                }
            }

            public void run() {
                try {
                    while (thread == Thread.currentThread()) {
                        serve();
                        message = null;
                    }
                    Logger.log("stop retrieving");
                } catch (SecurityException e) {
                    message = "SecurityException, allow networking!";
                    repaint();
                    Logger.log(message);
                }
            }
        };
        thread.start();
    }

    /**
     * Image receiving thread will stop in next round.
     */
    private void stop() {
        thread = null;
    }

    /**
     * Draws the most recently read image on screen.
     * @param g Graphics
     */
    public final void paint(final Graphics g) {
        Image localImg;
        synchronized (imgLock) {
            localImg = img;
            img = null;
        }
        if (localImg != null) {
            g.setColor(Colors.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
            g.drawImage(localImg, 0, 0, Graphics.TOP | Graphics.LEFT);
        }
        if (message != null) {
            g.setColor(Colors.RED);
            g.drawString(message, 0, 0, Graphics.TOP | Graphics.LEFT);
        }
    }

    /**
     * connect key press events to keyboardState for sending.
     * @param keyCode the keyCode from Canvas
     */
    protected final void keyPressed(final int keyCode) {
        keyboardState.onKeyDown(keyCode, getGameAction(keyCode));
        super.keyPressed(keyCode);
    }
    /**
     * connect key release events to keyboardState for sending.
     * @param keyCode the keyCode from Canvas
     */
    protected final void keyReleased(final int keyCode) {
        keyboardState.onKeyUp(keyCode, getGameAction(keyCode));
        super.keyReleased(keyCode);
    }
}
